<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>扩展运算符</title>
</head>
<body>
  <script>
    // 扩展运算符spread是三个点  ...   它好比rest参数的逆运算，将一个数组转为用逗号分隔的参数序列。
    console.log(...[1,2,3])
    // 1 2 3
    console.log(1, ...[2,3,5], 4)
    // 1 2 3 5 4
    // 扩展运算符后面还可以放置表达式。
    var x = 5
    const arr = [
      ...(x > 0 ? ['a'] : []),
      'b'
    ]
    console.log(arr)//["a", "b"]
    // 代替函数的apply方法： 由于...可以展开数组，所有不再需要apply方法，将数组转为函数的参数了。
    // ES5的写法
    function f(x,y,z) {
      console.log(x,y,z)
    }
    var args = [0, 1, 2]
    f.apply(null,args) // 0, 1, 2
    // es6的写法
    function f(x,y,z) {
      console.log(x,y,z)
    }
    var argus = [0, 1, 2]
    f(...argus) // 0 1 2
  </script>
  <script>
    // 扩展运算符的应用
    /**
     * 1.复制数组
     * 数组是复合的数据类型，直接复制的话，只是复制了指向底层数据结构的指针，而不是克隆一个全新的数组。
    */
   var a1 = [1, 2]
   var a2 = a1
   a2[0] = 2
   console.log(a1) // [2, 2]
   // 上面代码中，a2并不是a1的克隆，而是指向同一份数据的另一个指针。修改a2，会直接导致a1变化。
   // es5只能用变通方法来复制数组
   var a1 = [1, 2]
   var a2 = a1.concat() // concat 连接两个或多个数组，并返回已连接数组的副本。
   a2[0] = 2
   console.log(a1) // [1, 2]
   // 上面代码中a1会返回原数组的克隆，再修改a2就不会对a1产生影响。
   // 扩展运算符提供了复制数组的简便写法。
   var a1 = [1, 2]
   var a2 = [...a1]
   // 或者 var [...a2] = a1
   // 以上俩种写法，a2都是a1的克隆。
  </script>
  <script>
    // 2. 合并数组
    // 扩展运算符提供了数组合并的新写法
    var arr1 = ['a', 'b']
    var arr2 = ['c']
    var arr3 = ['d', 'e']
    // es5 的合并数组
    arr1.concat(arr2,arr3) // [ 'a', 'b', 'c', 'd', 'e' ]
    // es6 的合并数组
    var b = [...arr1, ...arr2, ...arr3]
    console.log(b) // [ 'a', 'b', 'c', 'd', 'e' ]
  </script>
  <script>
    // 3.与解构赋值结合使用
    // 扩展运算符可以与解构赋值结合起来，用于生成数组。
    var [first, ...rest] = [1,2,3,4,5]
    var [first, ...rest] = []
    // first // undefined
    // rest  // []
  </script>
  <script>
    // 4. 字符串
    // 扩展运算符还可以将字符串转为真正的数组。
    var a = [...'fuck']
    console.log(a) //["f", "u", "c", "k"]
    // 上面的写法有一个重要的好处，那就是能够正确识别四个字节的Unicode字符。
  </script>
  <script>
    // 5.实现了Iterator接口的对象
    /**
     * 任何定义了遍历器(Iterator)接口的对象，都可以用扩展运算符转为真正的数组。
    */
    let nodeList = document.querySelectorAll('div');
    let array = [...nodeList];
    /**
     * 上面代码中，querySelectorAll方法返回的是一个NodeList对象。它不是数组，而是一个类似数组的对象。
     * 这时，扩展运算符可以将其转为真正的数组，原因就在于NodeList对象实现了 Iterator 。
    */
   Number.prototype[Symbol.iterator] = function*() {
     let i = 0
     let num = this.valueOf()
     while(i < num) {
       yield i++ // yield相当于生成器函数特有的 return
     }
   }
   console.log([...5])// [0,1,2,3,4]
   /**
    * 上面代码中，先定义了Number对象的遍历器接口，扩展运算符将5自动转成Number实例以后，
    * 就会调用这个接口，就会返回自定义的结果，
    * 对于没有部署Iterator接口的类似数组的对象，扩展运算符就无法将其转为真正的数组。
   */
  let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
  }
  let d = [...arrayLike]
  // TypeError: Cannot spread non-iterable object.
  // 上面的代码中,arrayLike是一个类似数组的对象，但是没有部署Iterator接口，扩展运算符就会报错
  // 这时，可以使用Array.from方法将arrayLike转为真正的数组。
  </script>
  <script>
    // 6.Map和Set结构，Generator函数
    /**
     * 扩展运算符内部调用的是数据结构的Iterator接口，因此只要具有Iterator接口的对象
     * 都可使用扩展运算符，比如Map结构
    */
   var map = new Map([
     [1, 'one'],
     [2, 'two'],
     [3, 'three'],
   ])
   var j = [...map.keys()]
   console.log(j) // [1,2,3]
  </script>
  <script>
    // Generator函数运行后，返回一个遍历器对象，因此也可以使用扩展运算符。
    var go = function*() {
      yield 1
      yield 2
      yield 3
    }
    var arr4 = [...go()] // [1, 2, 3]
    console.log(arr4)
    /**
     * 上面代码中，变量go是一个 Generator 函数，执行后返回的是一个遍历器对象，
     * 对这个遍历器对象执行扩展运算符，就会将内部遍历得到的值，转为一个数组。
    */
  </script>
</body>
</html>